/**   
* @{#} ClassGenerator.java Create on 2013-6-22 下午1:43:39   
*   
* Copyright (c) 2012 by Baidu.   
*/
package objtable.core;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.google.common.base.Joiner;

import objtable.struct.DataType;
import objtable.struct.Row;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.CtField.Initializer;
import javassist.NotFoundException;

/**   
* @author <a href="mailto:zhangbo07@baidu.com">zhangbo07</a>  
* @version 1.0   
*/

public class TableGenerator {
	String tablename;
	List<FieldDesc> fds;
	Class<Row> cls;

	public TableGenerator(String name) {
		super();
		ReserveWord.checkWord(tablename);
		this.tablename = name;
		this.fds = new ArrayList<FieldDesc>();
	}

	public TableGenerator addField(DataType type, String name) {
		return addField(type, name, null);
	}

	public TableGenerator addField(DataType type, String name, Object def) {
		ReserveWord.checkWord(name);
		FieldDesc fd = new FieldDesc(type, name, def);
		fds.add(fd);
		return this;
	}

	protected TableGenerator addFieldWithoutCheck(DataType type, String name,
			Object def) {
		FieldDesc fd = new FieldDesc(type, name, def);
		fds.add(fd);
		return this;
	}

	public String[] getFields() {
		ArrayList<String> al = new ArrayList<String>();
		for (FieldDesc fd : fds) {
			al.add(fd.name);
		}
		String[] result = new String[al.size()];
		result = al.toArray(result);
		return result;
	}

	public DataType getFieldType(String field) {
		for (FieldDesc fd : fds) {
			if (fd.name.equals(field)) {
				return fd.type;
			}
		}
		return DataType.NULL;
	}

	public Class<Row> getRowClass() {
		regToCenter();

		return cls;
	}

	public void regToCenter() {
		if (TableCenter.getTable(tablename) == null) {
			genClass();
			//注册到中心
			TableCenter.putTable(tablename, this);
		} else {
			this.cls = TableCenter.getTable(tablename).cls;
		}
	}

	private String getClassName() {
		return "objtable.runtime." + tablename;
	}

	@SuppressWarnings("unchecked")
	private void genClass() {
		if (cls == null) {
			try {
				ClassPool cp = ClassPool.getDefault();
				CtClass ctClass = cp.makeClass(getClassName());
				beforeGenClass(ctClass);
				for (FieldDesc fd : fds) {
					CtField ctField = null;
					switch (fd.type) {
					case STRING: {
						ctField = new CtField(cp.get("java.lang.String"),
								fd.name, ctClass);
						//MVEL强制要求privete属性+set/get方法才能访问
						ctField.setModifiers(Modifier.PRIVATE);
						ctClass.addField(ctField,
								Initializer.constant((String) fd.value));
						break;
					}
					case INT: {
						ctField = new CtField(CtClass.intType, fd.name, ctClass);
						ctField.setModifiers(Modifier.PRIVATE);
						ctClass.addField(ctField,
								Initializer.constant((Integer) fd.value));
						break;
					}
					case LONG: {
						ctField = new CtField(CtClass.longType, fd.name,
								ctClass);
						ctField.setModifiers(Modifier.PRIVATE);
						ctClass.addField(ctField,
								Initializer.constant((Long) fd.value));
						break;
					}
					case DOUBLE: {
						ctField = new CtField(CtClass.doubleType, fd.name,
								ctClass);
						ctField.setModifiers(Modifier.PRIVATE);
						ctClass.addField(ctField,
								Initializer.constant((Double) fd.value));
						break;
					}
					case DATE: {
						ctField = new CtField(cp.get("java.util.Date"),
								fd.name, ctClass);
						ctField.setModifiers(Modifier.PRIVATE);
						ctClass.addField(ctField, "new java.util.Date("
								+ ((Date) fd.value).getTime() + "l)");
						break;
					}
					case LIST_ROW: {
						ctField = new CtField(cp.get("java.util.List"),
								fd.name, ctClass);
						ctField.setModifiers(Modifier.PRIVATE);
						ctClass.addField(ctField, "null");
						break;
					}
					case ROW: {
						ctField = new CtField(cp.get("objtable.struct.Row"),
								fd.name, ctClass);
						ctField.setModifiers(Modifier.PRIVATE);
						ctClass.addField(ctField, "null");
						break;
					}
					}
					String bigname = fd.getBigName();
					ctClass.addMethod(CtNewMethod.setter("set" + bigname,
							ctField));
					ctClass.addMethod(CtNewMethod.getter("get" + bigname,
							ctField));
				}
				afterGenClass(ctClass);
				cls = ctClass.toClass();
				//ctClass.writeFile("e:/test/");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	protected void afterGenClass(CtClass ctClass) {
		try {
			ctClass.addInterface(ctClass.getClassPool().getCtClass(
					Row.class.getName()));
			genRowInterfacePart(ctClass);
		} catch (CannotCompileException e) {
			e.printStackTrace();
		} catch (NotFoundException e) {
			e.printStackTrace();
		}
	}

	protected void beforeGenClass(CtClass ctClass) {
	}

	private String getBoxType(DataType type) {
		switch (type) {
		case STRING:
			return "String";
		case INT:
			return "Integer";
		case DOUBLE:
			return "Double";
		case LONG:
			return "Long";
		case DATE:
			return "java.util.Date";
		case LIST_ROW:
			return "java.util.List";
		case ROW:
			return "objtable.struct.Row";
		}
		return null;
	}

	private String boxCast(DataType type, String name) {
		switch (type) {
		case INT:
			return "Integer.valueOf(this." + name + ")";
		case DOUBLE:
			return "Double.valueOf(this." + name + ")";
		case LONG:
			return "Long.valueOf(this." + name + ")";
		default:
			return "this." + name;
		}
	}

	private boolean needCast(DataType type) {
		switch (type) {
		case INT:
			return true;
		case DOUBLE:
			return true;
		case LONG:
			return true;
		}
		return false;
	}

	private String getPriType(DataType type) {
		switch (type) {
		case INT:
			return "int";
		case DOUBLE:
			return "double";
		case LONG:
			return "long";
		}
		return null;
	}

	private void genRowInterfacePart(CtClass ctClass)
			throws CannotCompileException {
		//method public Object get(String name);
		String ifstr = "";
		for (FieldDesc fd : fds) {
			ifstr += ("if(name.equals(\"" + fd.name + "\")) return "
					+ boxCast(fd.type, fd.name) + ";");
		}
		String get = "public Object get(String name){" + ifstr
				+ "throw new objtable.exception.FieldNotFoundException(name,\""
				+ tablename + "\");}";
		ctClass.addMethod(CtNewMethod.make(get, ctClass));

		//method public int getInt(String name);
		ifstr = "";
		for (FieldDesc fd : fds) {
			if (fd.type == DataType.INT) {
				ifstr += ("if(name.equals(\"" + fd.name + "\")) return this."
						+ fd.name + ";");
			}
		}
		get = "public int getInt(String name){" + ifstr
				+ "throw new objtable.exception.FieldNotFoundException(name,\""
				+ tablename + "\");}";
		ctClass.addMethod(CtNewMethod.make(get, ctClass));

		//method public double getDouble(String name);
		ifstr = "";
		for (FieldDesc fd : fds) {
			if (fd.type == DataType.DOUBLE) {
				ifstr += ("if(name.equals(\"" + fd.name + "\")) return this."
						+ fd.name + ";");
			}
		}
		get = "public double getDouble(String name){" + ifstr
				+ "throw new objtable.exception.FieldNotFoundException(name,\""
				+ tablename + "\");}";
		ctClass.addMethod(CtNewMethod.make(get, ctClass));

		//method public long getLong(String name);
		ifstr = "";
		for (FieldDesc fd : fds) {
			if (fd.type == DataType.LONG) {
				ifstr += ("if(name.equals(\"" + fd.name + "\")) return this."
						+ fd.name + ";");
			}
		}
		get = "public long getLong(String name){" + ifstr
				+ "throw new objtable.exception.FieldNotFoundException(name,\""
				+ tablename + "\");}";
		ctClass.addMethod(CtNewMethod.make(get, ctClass));

		//method public String getString(String name);
		ifstr = "";
		for (FieldDesc fd : fds) {
			if (fd.type == DataType.STRING) {
				ifstr += ("if(name.equals(\"" + fd.name + "\")) return this."
						+ fd.name + ";");
			}
		}
		get = "public String getString(String name){" + ifstr
				+ "throw new objtable.exception.FieldNotFoundException(name,\""
				+ tablename + "\");}";
		ctClass.addMethod(CtNewMethod.make(get, ctClass));

		//method public void set(String name, Object value);
		ifstr = "";
		for (FieldDesc fd : fds) {
			if (needCast(fd.type)) {
				ifstr += ("if(name.equals(\"" + fd.name + "\")){this."
						+ fd.name + " = ((" + getBoxType(fd.type) + ")value)."
						+ getPriType(fd.type) + "Value();return;}");
			} else {
				ifstr += ("if(name.equals(\"" + fd.name + "\")){this."
						+ fd.name + " = (" + getBoxType(fd.type) + ")value;return;}");

			}
		}
		String set = "public void set(String name, Object value){" + ifstr
				+ "throw new objtable.exception.FieldNotFoundException(name,\""
				+ tablename + "\");}";
		ctClass.addMethod(CtNewMethod.make(set, ctClass));

		//method public String toString();
		ifstr = "";
		for (FieldDesc fd : fds) {
			ifstr += ("\"" + fd.name + ":\"+this." + fd.name + "+\",\"+");
		}
		String tostr = "public String toString(){return "
				+ ifstr.substring(0, ifstr.length() - 5) + ";}";
		ctClass.addMethod(CtNewMethod.make(tostr, ctClass));

		//method public int hashCode()
		ifstr = "";
		for (FieldDesc fd : fds) {
			if (isBasicType(fd.type)) {
				if (needCast(fd.type)) {
					ifstr += "h = (h*31 + this." + fd.name + ");";
				} else {
					ifstr += "h = (h*31 + this." + fd.name + ".hashCode());";
				}
			}
		}
		tostr = "public int hashCode(){int h=1;" + ifstr + "return h;}";
		ctClass.addMethod(CtNewMethod.make(tostr, ctClass));

		//method public boolean equals(Object obj);
		ArrayList<String> eq = new ArrayList<String>();
		//不比较嵌套对象，ROW和LIST_ROW不在比较范围
		for (FieldDesc fd : fds) {
			if (isBasicType(fd.type)) {
				switch (fd.type) {
				case INT:
				case LONG:
					eq.add("(this." + fd.name + "==o.get" + fd.getBigName()
							+ "())");
					break;
				case DOUBLE:
					eq.add("(Double.doubleToLongBits(this." + fd.name
							+ ")==Double.doubleToLongBits(o.get"
							+ fd.getBigName() + "()))");
					break;
				//equals部分
				case STRING:
				case DATE:
					eq.add("(this." + fd.name + ".equals(o.get"
							+ fd.getBigName() + "()))");
					break;
				}
			}
		}
		ifstr = Joiner.on(" && ").join(eq);
		tostr = "public boolean equals(Object obj){if(!(obj instanceof "
				+ getClassName() + ")) return false; " + getClassName()
				+ " o = (" + getClassName() + ")obj; return " + ifstr + ";}";
		ctClass.addMethod(CtNewMethod.make(tostr, ctClass));

		//method public String getTableName();
		tostr = "public String getTableName(){return \"" + this.tablename
				+ "\";}";
		ctClass.addMethod(CtNewMethod.make(tostr, ctClass));
	}

	private boolean isBasicType(DataType type) {
		return type == DataType.INT || type == DataType.DOUBLE
				|| type == DataType.LONG || type == DataType.STRING
				|| type == DataType.DATE;
	}

	public final Row newRow() {
		regToCenter();

		try {
			Row o = cls.newInstance();
			for (FieldDesc fd : fds) {
				o.set(fd.name, fd.value);
			}
			return o;
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(StatusCode.FAIL_NEW_ROW);
		}

		return null;
	}

	class FieldDesc {
		DataType type;
		String name;
		Object value;
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
		String today;

		public FieldDesc(DataType t, String n, Object v) {
			this.type = t;
			this.name = n;
			this.value = v;
			this.today = sdf.format(new Date());
			if (value == null) {
				setDefault();
			}
		}

		private void setDefault() {
			switch (type) {
			case STRING:
				value = "";
				break;
			case INT:
				value = 0;
				break;
			case LONG:
				value = 0l;
				break;
			case DOUBLE:
				value = 0.0;
				break;
			case DATE:
				try {
					value = sdf.parse(today);
				} catch (ParseException e) {
					e.printStackTrace();
				}
				break;
			case LIST_ROW:
				break;
			case ROW:
				break;
			default:
				value = null;
			}
		}

		public String getBigName() {
			return name.substring(0, 1).toUpperCase()
					+ name.substring(1, name.length());
		}
	}
}
